// SPDX-License-Identifier: GPL-2.0
/******************************************************************************
 *    NAND Flash Controller nand Device Driver
 *    Copyright (c) 2009-2010 by Hisilicon.
 *    All rights reserved.
 * ***
 *
 ******************************************************************************/

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/rawnand.h>
#include <linux/mtd/onenand.h>
#include <linux/mtd/partitions.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <asm/errno.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/kallsyms.h>

#include "hibase.h"
#include "mtdcore.h"
#include "hinfc301.h"
#include "hifmcv100.h"
#include "hinand.h"
#ifdef HW_ONT_CUT_VERSION
#include <linux/kallsyminfo.h>
#endif

#ifndef __tagtable
#define __tagtable(tag, fn) \
static const struct tagtable __tagtable_##fn __used __section(".taglist.init") = { tag, fn }
#endif

#ifdef CONFIG_MTD_PARTITIONS
#define mtd_has_partitions()  (1)
#else
#define mtd_has_partitions()  (0)
#endif
#define ioremap_nocache(phy, sz)        ioremap(phy, sz)
#ifndef MTD_CHIP_ID_LEN
#define MTD_CHIP_ID_LEN 8
#endif
static struct proc_dir_entry *proc_file_entry;

static unsigned char g_nand_ids[MTD_CHIP_ID_LEN] = {0};
unsigned long g_default_oob_size;

/* save save_cmd_line for more lifetime */
char g_flash_cmd_line[COMMAND_LINE_SIZE];
EXPORT_SYMBOL_GPL(g_flash_cmd_line);

#ifdef CONFIG_MTD_CMDLINE_PARTS
static const char * const part_probes[] = { "cmdlinepart", NULL, };
#endif

static struct partition_info patn_info;

static int __init parse_nand_partitions(const struct tag *tag)
{
	int i;

	if (tag->hdr.size <= 2) {
		pr_info("%s(%d): tag->hdr.size <= 2\n", __func__, __LINE__);
		return 0; /* __tagtable generally returns 0, abnormal branch will record it */
	}

	patn_info.parts_num =
		(tag->hdr.size - 2) /
		(sizeof(struct partition_entry) / sizeof(int));
	if (patn_info.parts_num > MAX_MTD_PARTITIONS) {
		pr_info("parts_num > %u\n", MAX_MTD_PARTITIONS);
		return 0; /* __tagtable generally returns 0, abnormal branch will record it */
	}
	memcpy(patn_info.entry, &tag->u,
		patn_info.parts_num * sizeof(struct partition_entry));

	for (i = 0; i < patn_info.parts_num; i++) {
		patn_info.parts[i].name = patn_info.entry[i].name;
		patn_info.parts[i].size = (patn_info.entry[i].length);
		patn_info.parts[i].offset = (patn_info.entry[i].start);
		patn_info.parts[i].mask_flags = 0;
	}

	return 0;
}

/* turn to ascii is "HiNp" */
__tagtable(HINP_ASCII, parse_nand_partitions);

static ssize_t flashchipid_read_interval(struct file *file,
	char __user *buffer, size_t buflen, loff_t *fpos)
{
	int count;
	char *kbuf = NULL;

	if (*fpos == HI_READ_CHIPID_MAGIC)
		return 0;

	/* copy chip id to user as string,
	 * include MTD_CHIP_ID_LEN*2 chars, '\n' and '\0'
	*/
	kbuf = kzalloc(MTD_CHIP_ID_LEN * 2 + 2, GFP_KERNEL);
	if (!kbuf)
		return -ENOMEM;

	count = snprintf(kbuf, MTD_CHIP_ID_LEN * 2 + 2,
				"%02x%02x%02x%02x%02x%02x%02x%02x\n",
				g_nand_ids[0], g_nand_ids[1], g_nand_ids[2],
				g_nand_ids[3], g_nand_ids[4], g_nand_ids[5],
				g_nand_ids[6], g_nand_ids[7]);

	if (copy_to_user(buffer, kbuf, strlen(kbuf) + 1)) {
		kfree(kbuf);
		return -EFAULT;
	}

	*fpos = HI_READ_CHIPID_MAGIC;
	kfree(kbuf);
	return count;
}

/* Support for /proc/flashchipid */
static ssize_t flashchipid_write_interval(struct file *file,
	const char __user *buffer, size_t buflen, loff_t *fpos)
{
	return (ssize_t)buflen;
}

const struct proc_ops proc_nandflashchipid = {
	.proc_read = flashchipid_read_interval,
	.proc_open = NULL,
	.proc_write = flashchipid_write_interval,
	.proc_release = NULL,
};


#ifdef CONFIG_RTOS_SETUP_NAND_MAX_CHIP
unsigned int flash_num = 1;

static int __init cmdline_flashnum_setup(char *str)
{
	ssize_t ret;

	if (!str)
		return 0;

	ret = kstrtou32(str, 0, &flash_num);
	if (ret) {
		pr_err("hinand: flash num parse error, set to default 1\n");
		goto fail;
	}

	if (flash_num > CONFIG_FMC_MAX_CHIP) {
		pr_err("hinand: flash num %d is invalid, set to default 1\n", flash_num);
		goto fail;
	}
	return 1;
fail:
	flash_num = 1;
	return 0;
}
__setup("flashnum=", cmdline_flashnum_setup);
#endif

#ifdef CONFIG_RTOS_STORAGE_FMC_CHIP_MIX
void hi_refill_chipinfo(struct hinfc_host *host, unsigned int numchips)
{
	struct nand_chip *chip = host->chip;
	struct mtd_info *mtd = host->mtd;
	int ori_cs = host->chipselect;
	int curr_cs;

	if (chip->numchips < numchips) {
		/* SET_FEATURE besides ori_cs chip */
		for (curr_cs = chip->numchips; curr_cs < numchips; curr_cs++) {
			chip->select_chip(mtd, curr_cs);
			fmc_flash_feature_init(mtd);
		}
		 /* restore ori_cs */
		chip->select_chip(mtd, ori_cs);
		 /* modify numchips and mtd size */
		chip->numchips = numchips;
		mtd->size = numchips * chip->chipsize;
	}
}
#endif

#ifdef HW_ONT_CUT_VERSION
static unsigned long kallsyms_lookup_name_from_global(const char *name)
{
	int i = 0;
	unsigned long paddr = 0;

	for (; i < ARRAY_SIZE(g_sym_addr_map); i++) {
		if (strcmp(g_sym_addr_map[i].sym_name, name) == 0) {
			paddr = g_sym_addr_map[i].sym_addr;
			break;
		}
	}

	if (paddr == 0)
		return 0;
#ifdef CONFIG_THUMB2_KERNEL
	if (paddr)
		paddr |= 1;
#endif

	return paddr;
}

static unsigned long kallsyms_lookup_name_for_tiny(const char *name)
{
	static unsigned long (*local_trustlist)(const char *name);
	static void *(*local_symbol)(const char *symbol);
	unsigned long paddr;

	paddr = kallsyms_lookup_name_from_global(name);
	if (paddr != 0)
		return paddr;

	if (local_trustlist == NULL) {
		local_trustlist = (unsigned long(*)(const char *))kallsyms_lookup_name_from_global(
			"find_trustlist_symbols");
	}

	paddr = local_trustlist(name);
	if (paddr != 0)
		return paddr;

	if (local_symbol == NULL) {
		local_symbol = (void *(*)(const char *))kallsyms_lookup_name_from_global(
			"__symbol_get");
	}

	return (unsigned long)local_symbol(name);
}
#endif

int local_add_mtd_device(struct mtd_info *mtd)
{
	int (*add_mtd_device)(struct mtd_info *mtd) = NULL;
#ifdef HW_ONT_CUT_VERSION
	add_mtd_device = (void(*))kallsyms_lookup_name_for_tiny("add_mtd_device");
#else
#ifdef CONFIG_RTOS_FIND_TRUSTLIST_SYMBOLS
	add_mtd_device = (void(*))find_trustlist_symbols("add_mtd_device");
#endif
	if (add_mtd_device == NULL)
		add_mtd_device = (void(*))symbol_get(add_mtd_device);
#endif

#ifdef CONFIG_THUMB2_KERNEL
	add_mtd_device = (void(*))((unsigned long)add_mtd_device | 1);
#endif
	return add_mtd_device(mtd);
}
EXPORT_SYMBOL_GPL(local_add_mtd_device);

int local_parse_mtd_partitions(
	struct mtd_info *master, const char * const *types, struct mtd_part_parser_data *data)
{
	int (*parse_mtd_partitions)(struct mtd_info *master, const char * const *types,
		struct mtd_part_parser_data *data) = NULL;
#ifdef HW_ONT_CUT_VERSION
	parse_mtd_partitions = (void(*))kallsyms_lookup_name_for_tiny("parse_mtd_partitions");
#else
#ifdef CONFIG_RTOS_FIND_TRUSTLIST_SYMBOLS
	parse_mtd_partitions = (void(*))find_trustlist_symbols("parse_mtd_partitions");
#endif
	if (parse_mtd_partitions == NULL)
		parse_mtd_partitions = (void(*))symbol_get(parse_mtd_partitions);
#endif

#ifdef CONFIG_THUMB2_KERNEL
	parse_mtd_partitions = (void(*))((unsigned long)parse_mtd_partitions | 1);
#endif
	return parse_mtd_partitions(master, types, data);
}
EXPORT_SYMBOL_GPL(local_parse_mtd_partitions);

static int hi_probe_pltdev(struct platform_device *pltdev)
{
	int result = 0;
	struct hinfc_host *host = NULL;
	struct nand_chip *chip = NULL;
	struct mtd_info *mtd = NULL;

	int size = sizeof(struct hinfc_host) + sizeof(struct nand_chip);

	host = kzalloc(size, GFP_KERNEL);
	if (!host)
		return -ENOMEM;
	platform_set_drvdata(pltdev, host);

	host->dev = &pltdev->dev;
	host->chip = chip = (struct nand_chip *)&host[1];
	host->mtd = mtd = (struct mtd_info *)&(chip->base.mtd);
	host->iobase = ioremap(pltdev->resource[0].start,
		pltdev->resource[0].end - pltdev->resource[0].start + 1);
	if (!host->iobase) {
		dev_err(&pltdev->dev, "ioremap failed\n");
		kfree(host);
		return -EIO;
	}

	mtd->priv = chip;
	mtd->owner = THIS_MODULE;
	mtd->name = (char *)(pltdev->name);
	INIT_LIST_HEAD(&mtd->partitions);
	chip->legacy.IO_ADDR_R = chip->legacy.IO_ADDR_W = ioremap_nocache(
		pltdev->resource[1].start,
		pltdev->resource[1].end - pltdev->resource[1].start + 1);
	if (!chip->legacy.IO_ADDR_R) {
		dev_err(&pltdev->dev, "ioremap failed\n");
		iounmap(host->iobase);
		kfree(host);
		return -EIO;
	}

	host->sysreg = ioremap_nocache(pltdev->resource[2].start,
		pltdev->resource[2].end - pltdev->resource[2].start + 1);
	if (!host->sysreg) {
		dev_err(&pltdev->dev, "ioremap failed\n");
		iounmap(chip->legacy.IO_ADDR_W);
		iounmap(host->iobase);
		kfree(host);
		return -EIO;
	}

	if (strstr(g_flash_cmd_line, "flash_control=fmc") != NULL) {
		if (fmc_nand_init(host, chip)) {
			dev_err(&pltdev->dev, "failed to allocate device buffer.\n");
			result = -ENOMEM;
			goto err;
		}
		if (hifmc_chip_reset_test(host)) {
			result = -ENODEV;
			goto err;
		}
	} else {
		if (hinfc301_nand_init(host, chip)) {
			dev_err(&pltdev->dev, "failed to allocate device buffer.\n");
			result = -ENOMEM;
			goto err;
		}

		if (hinfc301_chip_reset_test(host)) {
			result = -ENODEV;
			goto err;
		}
	}

	if (strstr(g_flash_cmd_line, "flash_num=2") != NULL)
		flash_num = 2;
#ifdef CONFIG_RTOS_SETUP_NAND_MAX_CHIP
	if (nand_scan(chip, flash_num)) {
#else
	if (nand_scan(chip, CONFIG_NAND_MAX_CHIP)) {
#endif
		result = -ENXIO;
		goto err;
	}
#ifdef CONFIG_RTOS_STORAGE_FMC_CHIP_MIX
	hi_refill_chipinfo(host, flash_num);
#endif

	/* detect 2 pieces of Nandflash, second nandflash need feature init */
	if (flash_num == 2) {
		host->chipselect = 0;  /* second flash */
		fmc_flash_feature_init(mtd);
		host->chipselect = 1;  /* first flash */
	}
#ifdef CONFIG_RTOS_OPTIMIZE_ONT
	mtd->read_chip_id(mtd, g_nand_ids, MTD_CHIP_ID_LEN);
#endif
	proc_file_entry = proc_create("flashchipid", 0440, 0, &proc_nandflashchipid);
	if (!proc_file_entry) {
		pr_info("proc_create flashchipid failed\n");
		result = -ENOMEM;
		goto err;
	}
	if (mtd_has_partitions()) {
		int nr_parts = 0;
#ifdef CONFIG_MTD_CMDLINE_PARTS
		nr_parts = local_parse_mtd_partitions(mtd, part_probes, 0);
#endif
		if (nr_parts > 0)
			return 0;

		result = -ENODEV;
		if (strstr(g_flash_cmd_line, "flash_control=fmc") == NULL) {
			(void)mtd_device_unregister(mtd);
			nand_cleanup(chip);
		}
		goto err;
	}

	if (!local_add_mtd_device(mtd))
		return 0;
	result = -ENODEV;
	(void)mtd_device_unregister(mtd);

err:
	if (proc_file_entry)
		remove_proc_entry("flashchipid", NULL);
	if (host->buffer) {
		dma_free_coherent(host->dev,
			(NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE),
			host->buffer, host->dma_buffer);
		host->buffer = NULL;
	}
	if (chip->legacy.IO_ADDR_W)
		iounmap(chip->legacy.IO_ADDR_W);
	if (host->iobase)
		iounmap(host->iobase);
	if (host->sysreg)
		iounmap(host->sysreg);

	kfree(host);
	platform_set_drvdata(pltdev, NULL);

	return result;
}

int hi_remove(struct hinfc_host *host)
{
	if (host->buffer) {
		dma_free_coherent(host->dev,
			(NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE),
			host->buffer, host->dma_buffer);
		host->buffer = NULL;
	}
	if (host->sysreg)
		iounmap(host->sysreg);
	if (host->chip->legacy.IO_ADDR_W)
		iounmap(host->chip->legacy.IO_ADDR_W);
	if (host->iobase)
		iounmap(host->iobase);

	return 0;
}

int hi_remove_pltdrv(struct platform_device *pltdev)
{
	struct hinfc_host *host = platform_get_drvdata(pltdev);

	if (strstr(g_flash_cmd_line, "flash_control=fmc") != NULL) {
		if (!host)
			return -EINVAL;
	} else {
		hinfc301_controller_enable(host, 0);
	}

	mtd_device_unregister(host->mtd);

	if (proc_file_entry)
		remove_proc_entry("flashchipid", NULL);

	(void)hi_remove(host);  /* always return 0 */

	kfree(host);
	platform_set_drvdata(pltdev, NULL);

	return 0;
}

static struct platform_driver hifmc_pltdrv = {
	.driver.name = "hinand",
	.probe = hi_probe_pltdev,
	.remove = hi_remove_pltdrv,
#ifdef CONFIG_PM
	.suspend = NULL,
	.resume = NULL,
#endif
};

static struct resource hifmc_resources[] = {
	{
		.start = FMC_REG_BASE,
		.end = FMC_REG_BASE + FMC_REG_BASE_ADDRESS_LEN,
		.flags = IORESOURCE_MEM,
	},
	{
		.start = FMC_MEM_BASE,
		.end = FMC_MEM_BASE + FMC_BUFFER_BASE_ADDRESS_LEN,
		.flags = IORESOURCE_MEM,
	},
	{
		.start = CONFIG_HINFC301_PERIPHERY_REGBASE,
		.end = CONFIG_HINFC301_PERIPHERY_REGBASE + 100,
		.flags = IORESOURCE_MEM,
	},
};

static struct platform_device hifmc_pltdev = {
	.name = "hinand",
	.id = -1,
	.dev.platform_data = NULL,
	.dev.dma_mask = (u64 *)~0,
	.dev.coherent_dma_mask = (u64) ~0,
	.dev.release = NULL,
	.num_resources = ARRAY_SIZE(hifmc_resources),
	.resource = hifmc_resources,
};

static struct resource hinfc301_resources[] = {
	{
		.start = CONFIG_HINFC301_REG_BASE_ADDRESS,
		.end = CONFIG_HINFC301_REG_BASE_ADDRESS +
			HINFC301_REG_BASE_ADDRESS_LEN,
		.flags = IORESOURCE_MEM,
	},
	{
		.start = CONFIG_HINFC301_BUFFER_BASE_ADDRESS,
		.end = CONFIG_HINFC301_BUFFER_BASE_ADDRESS +
			HINFC301_BUFFER_BASE_ADDRESS_LEN,
		.flags = IORESOURCE_MEM,
	},
	{
		.start = CONFIG_HINFC301_PERIPHERY_REGBASE,
		.end = CONFIG_HINFC301_PERIPHERY_REGBASE + 100,
		.flags = IORESOURCE_MEM,
	},
};

static struct platform_driver hinfc301_pltdrv = {
	.driver.name = "hinand",
	.probe = hi_probe_pltdev,
	.remove = hi_remove_pltdrv,
#ifdef CONFIG_PM
	.suspend = hinfc301_suspend,
	.resume = hinfc301_resume,
#endif
};

static struct platform_device hinfc301_pltdev = {
	.name = "hinand",
	.id = -1,
	.dev.platform_data = NULL,
	.dev.dma_mask = (u64 *)~0,
	.dev.coherent_dma_mask = (u64) ~0,
	.dev.release = NULL,
	.num_resources = ARRAY_SIZE(hinfc301_resources),
	.resource = hinfc301_resources,
};

static int __init hinand_module_init(void)
{
	int result = 0;
	struct platform_driver *reg_drv = &hinfc301_pltdrv;
	struct platform_device *reg_dev = &hinfc301_pltdev;

	nand_spl_ids_init();
	memcpy(g_flash_cmd_line, save_cmd_line, COMMAND_LINE_SIZE);
	if (strstr(g_flash_cmd_line, "flash_control=fmc") != NULL) {
		if (strstr(g_flash_cmd_line, "flash_chip=spinor") != NULL)
			return 0;

		/* try to get base addr from DTS */
		hinand_of_node_parse(&hifmc_pltdev, "hw,hifmc");
		reg_drv = &hifmc_pltdrv;
		reg_dev = &hifmc_pltdev;
	} else if (strstr(g_flash_cmd_line, "flash_control=sfc") != NULL) {
		return 0;
	}

	pr_info("Hisilicon Nand Flash Controller V301 Device Driver, Version 1.10\n");

	result = platform_driver_register(reg_drv);
	if (result < 0)
		return result;

	result = platform_device_register(reg_dev);
	if (result < 0) {
		platform_driver_unregister(reg_drv);
		return result;
	}

	return result;
}

static void __exit hinand_module_exit(void)
{
	struct platform_driver *reg_drv = NULL;
	struct platform_device *reg_dev = NULL;

	reg_drv = &hinfc301_pltdrv;
	reg_dev = &hinfc301_pltdev;

	if (strstr(g_flash_cmd_line, "flash_control=fmc") != NULL) {
		if (strstr(g_flash_cmd_line, "flash_chip=spinor") != NULL)
			return;

		reg_drv = &hifmc_pltdrv;
		reg_dev = &hifmc_pltdev;
	} else if (strstr(g_flash_cmd_line, "flash_control=sfc") != NULL) {
		return;
	}

	platform_driver_unregister(reg_drv);
	platform_device_unregister(reg_dev);
}

MODULE_IMPORT_NS(HW_RTOS_NS);
module_init(hinand_module_init);
module_exit(hinand_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Cai ZhiYong");
MODULE_DESCRIPTION("Hisilicon Nand Flash Controller Device Driver, Version 1.10");
